"use strict";
const GAME_CONFIG = {
    objectConfigs: {
        default: {
            objects: ["", "🚩", "🌻", "🌼", "🌲", "🌳", "🏡", "🚧", "🤷‍", "📦"],
            player: 8,
            box: 9,
            target: 1,
            solids: [4, 5, 6, 7],
            grounds: [0, 1, 2, 3]
        }
    },
    levels: [
        {
            map: [
                [6, 5, 4, 7, 5, 6],
                [5, 4, 0, 8, 0, 5],
                [4, 0, 9, 0, 0, 4],
                [5, 0, 0, 0, 0, 4],
                [4, 1, 0, 2, 3, 4],
                [4, 7, 4, 4, 5, 6]
            ],
            objectConfigId: "default"
        },
        {
            map: [
                [6, 5, 4, 7, 5, 6],
                [5, 4, 0, 8, 0, 5],
                [4, 0, 9, 0, 0, 4],
                [5, 0, 0, 0, 0, 4],
                [4, 1, 0, 2, 3, 4],
                [4, 7, 4, 4, 5, 6]
            ],
            objectConfigId: "default"
        }
    ]
};
/**
 * 遍历 map 每格调用一次回调函数
 * @param map 遍历的目标地图
 * @param callback 回调函数
 */
function forEach2DMap(map, callback) {
    for (let x = 0; x < map.length; x++) {
        const row = map[x];
        for (let y = 0; y < row.length; y++) {
            callback(x, y, row[y]);
        }
    }
}
/**
 * 遍历 map 每格调用一次测试函数，如果数组中至少有一个元素满足测试函数，则返回 true，否则返回 false。
 * @param map 遍历的目标地图
 * @param callback 测试函数，若测试函数返回 true 则立即停止遍历，并且 some2DMap 返回 true
 */
function some2DMap(map, callback) {
    for (let x = 0; x < map.length; x++) {
        const row = map[x];
        for (let y = 0; y < row.length; y++) {
            if (callback(x, y, row[y]))
                return true;
        }
    }
    return false;
}
/**
 * 翻转地图：交换地图的xy，生成新地图
 * @param map 遍历的目标地图
 */
function flipMap(map) {
    const newMap = [];
    forEach2DMap(map, (x, y, item) => {
        if (!(newMap[y] instanceof Array)) {
            newMap[y] = [];
        }
        newMap[y][x] = item;
    });
    return newMap;
}
/**
 * 取反色
 * @param color 十六进制颜色 例如 #FFFFFF
 */
function colorReverse(color) {
    var colorHex = Number("0x" + color.replace(/#/g, ""));
    var str = "000000" + (0xffffff - colorHex).toString(16);
    return "#" + str.substring(str.length - 6, str.length);
}
/**
 * 游戏类
 */
class Game {
    constructor(canvas) {
        this.levelIndex = 0;
        this.canvas = canvas;
        this.ctx = canvas.getContext("2d");
        canvas.addEventListener("keydown", this.onKeyDown.bind(this), true);
        canvas.focus();
    }
    /**
     * 键盘事件处理函数
     * @param e 键盘事件
     */
    onKeyDown(e) {
        if (37 > e.keyCode || e.keyCode > 40) {
            return;
        }
        this.moveTo(e.keyCode - 37);
    }
    /**
     * 移动一格
     * @param direction 方向 0:左 1:上 2:右 3:下
     */
    moveTo(direction) {
        const i = [[-1, 0], [0, -1], [1, 0], [0, 1]];
        this.nextStep(i[direction][0], i[direction][1]);
    }
    /**
     * 下一步
     * @param xi x增量
     * @param yi y增量
     */
    nextStep(xi, yi) {
        if (!this.curMap)
            throw "Render Error: Missing curMap!";
        if (!this.refMap)
            throw "Render Error: Missing refMap!";
        if (!this.objCfg)
            throw "Render Error: Missing objCfg!";
        console.log("增量:", xi, yi);
        const lp = this.getPlayerPosition(this.curMap);
        console.log("原位:", lp.x, lp.y);
        const np = { x: lp.x + xi, y: lp.y + yi };
        if (np.x > this.curMap.length - 1 || np.x < 0)
            return; // 越界就跳过接下来的步骤
        if (np.y > this.curMap[np.x].length - 1 || np.y < 0)
            return; // 越界就跳过接下来的步骤
        console.log("新位:", np.x, np.y);
        if (this.objCfg.solids.includes(this.curMap[np.x][np.y])) {
            // 如果前面是墙壁
            return; // 就跳过接下来的步骤
        }
        else if (this.objCfg.grounds.includes(this.curMap[np.x][np.y])) {
            // 如果前面是可经过的固定物体
            this.curMap[np.x][np.y] = this.curMap[lp.x][lp.y]; // 人到前面去
            this.curMap[lp.x][lp.y] = this.refMap[lp.x][lp.y]; // 原来的位置放固定物体
        }
        else if (this.curMap[np.x][np.y] === this.objCfg.box) {
            // 如果前面是箱子
            const xp = { x: lp.x + xi * 2, y: lp.y + yi * 2 };
            if (xp.x > this.curMap.length - 1 || xp.x < 0)
                return; // 越界就跳过接下来的步骤
            if (xp.y > this.curMap[np.x].length - 1 || xp.y < 0)
                return; // 越界就跳过接下来的步骤
            console.log("新新位:", xp.x, xp.y);
            if (this.objCfg.grounds.includes(this.curMap[xp.x][xp.y])) {
                // 如果前面是可经过的固定物体
                this.curMap[xp.x][xp.y] = this.curMap[np.x][np.y]; // 箱子到前面的前面去
                this.curMap[np.x][np.y] = this.curMap[lp.x][lp.y]; // 人到前面去
                this.curMap[lp.x][lp.y] = this.refMap[lp.x][lp.y]; // 原来的位置放固定物体
            }
        }
        this.render();
        this.checkWin();
    }
    /**
     * 渲染
     */
    render() {
        if (!this.curMap)
            throw "Render Error: Missing curMap!";
        const size = 32;
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        forEach2DMap(this.curMap, (x, y, objectId) => {
            if (!this.objCfg)
                throw "Render Error: Missing objCfg!";
            const object = this.objCfg.objects[objectId];
            // this.ctx.fillStyle = object.fillStyle;
            // this.ctx.fillRect(x * size, y * size, size, size);
            // this.ctx.fillStyle = colorReverse(object.fillStyle);
            this.ctx.font = "22px serif";
            // this.ctx.textAlign = "center";
            this.ctx.fillText(object, x * size + (size - 22) / 2 - 4, y * size + (size - 22) / 2 + 22 - 4);
        });
    }
    /**
     * 开始游戏
     */
    play() {
        this.startLevel(0);
    }
    /**
     * 开始关卡
     */
    startLevel(newLevel) {
        this.levelIndex = Math.min(newLevel, GAME_CONFIG.levels.length - 1);
        if (this.levelIndex in GAME_CONFIG.levels) {
            this.level = GAME_CONFIG.levels[this.levelIndex];
        }
        else {
            throw "Config Parse Error: This objectConfigId does not exist on the objectConfigs!";
        }
        if (this.level.objectConfig) {
            this.objCfg = this.level.objectConfig;
        }
        else if (this.level.objectConfigId) {
            if (GAME_CONFIG.objectConfigs.hasOwnProperty(this.level.objectConfigId)) {
                this.objCfg = GAME_CONFIG.objectConfigs[this.level.objectConfigId];
            }
            else {
                throw "Config Parse Error: This objectConfigId does not exist on the objectConfigs!";
            }
        }
        else {
            throw "Config Parse Error: Missing objectConfig!";
        }
        this.curMap = flipMap(this.level.map); // 翻转地图，并赋值给关卡地图和地图
        this.refMap = this.generateReferenceMap(this.curMap); // 参照物地图，用于恢复经过地点的对象id
        console.log(this.curMap, this.refMap);
        this.render();
    }
    /**
     * 下一关
     */
    nextLevel() {
        this.startLevel(this.levelIndex + 1);
    }
    /**
     * 获取玩家坐标点
     */
    getPlayerPosition(map) {
        const point = { x: 0, y: 0 };
        forEach2DMap(map, (x, y, objectId) => {
            if (objectId === this.objCfg.player) {
                point.x = x;
                point.y = y;
                return true;
            }
        });
        return point;
    }
    /**
     * 检查胜负
     */
    checkWin() {
        if (!this.curMap)
            throw "Render Error: Missing curMap!";
        const isWin = !some2DMap(this.curMap, (x, y, objectId) => objectId === this.objCfg.box &&
            this.refMap[x][y] !== this.objCfg.target); // 只要有一个箱子不处于目标位置则不获胜，若所有箱子的位置都处于目标位置则获胜
        if (isWin) {
            setTimeout(() => {
                alert("获胜！");
                this.nextLevel();
            });
        }
    }
    /**
     * 生成参考地图
     * @param map 原地图
     */
    generateReferenceMap(map) {
        if (!this.objCfg)
            throw "Render Error: Missing objCfg!";
        const newMap = [];
        forEach2DMap(map, (x, y, objectId) => {
            if (!(newMap[x] instanceof Array)) {
                newMap[x] = [];
            }
            if (objectId === this.objCfg.box || objectId === this.objCfg.player) {
                newMap[x][y] = this.objCfg.grounds[0]; // 替换非固定物体为第一个地面
            }
            else {
                newMap[x][y] = objectId;
            }
        });
        return newMap;
    }
}
